{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第8章 python装饰器\n",
    "装饰器本质上是一个Python函数，它可以让其他函数在不需要做任何代码变动的前提下增加额外功能，装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景，比如：插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计，有了装饰器，我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本章将从以下方面来介绍Python中的装饰器：\n",
    "\n",
    "* 函数装饰器\n",
    "\n",
    "* 类装饰器\n",
    "* 内置装饰器\n",
    "* 装饰器链\n",
    "* 需要注意的地方"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. 函数装饰器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.1 简单装饰器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "这是一个简单的装饰器\n",
      "i am foo\n"
     ]
    }
   ],
   "source": [
    "def use_logging(func):\n",
    "\n",
    "    def wrapper():\n",
    "        print('这是一个简单的装饰器')\n",
    "        return func()   # 把 foo 当做参数传递进来时，执行func()就相当于执行foo()\n",
    "    return wrapper\n",
    "\n",
    "def foo():\n",
    "    print('i am foo')\n",
    "\n",
    "foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的是函数对象 wrapper，这条语句相当于  foo = wrapper\n",
    "foo()                   # 执行foo()就相当于执行 wrapper()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.2 @语法糖\n",
    "@ 符号就是装饰器的语法糖，它放在函数开始定义的地方，这样就可以省略最后一步再次赋值的操作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "这是一个简单的装饰器\n",
      "i am foo\n"
     ]
    }
   ],
   "source": [
    "def use_logging(func):\n",
    "\n",
    "    def wrapper():\n",
    "        print('这是一个简单的装饰器')\n",
    "        return func()   # 把 foo 当做参数传递进来时，执行func()就相当于执行foo()\n",
    "    return wrapper\n",
    "\n",
    "@use_logging\n",
    "def foo():\n",
    "    print('i am foo')\n",
    "\n",
    "foo()                   # 执行foo()就相当于执行 wrapper()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如上所示，有了 @ ，我们就可以省去foo = use_logging(foo)这一句了，直接调用 foo() 即可得到想要的结果。foo() 函数不需要做任何修改，只需在定义的地方加上装饰器，调用的时候还是和以前一样，如果我们有其他的类似函数，我们可以继续调用装饰器来修饰函数，而不用重复修改函数或者增加新的封装。这样，我们就提高了程序的可重复利用性，并增加了程序的可读性。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.3 \\*args、**kwargs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果我的业务逻辑函数 foo 需要参数该怎么办？比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def foo(name):\n",
    "    print(\"i am %s\" % name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这时，我们可以在定义wrapper函数的时候指定参数。并且当装饰器不知道 foo 到底有多少个参数时，我们可以用 *args 来代替"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def wrapper(*args):\n",
    "        print('test')\n",
    "        return func(*args)\n",
    "    return wrapper"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如此一来，不管foo定义了多少个参数，我们都可以完整地传递到 func 中去。这样就不影响 foo 的业务逻辑了。这时你可能还有疑问，如果 foo 函数还定义了一些关键字参数呢？比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def foo(name, age=None, height=None):\n",
    "    print(\"I am %s, age %s, height %s\" % (name, age, height))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这时，我们就可以把 wrapper 函数指定关键字函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def wrapper(*args, **kwargs):\n",
    "        # args是一个数组，kwargs一个字典\n",
    "        print('test')\n",
    "        return func(*args, **kwargs)\n",
    "    return wrapper"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.4 类方法的函数装饰器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.8001229763031006\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "def decorator(func):\n",
    "    def wrapper(me_instance):\n",
    "        start_time = time.time()\n",
    "        func(me_instance)\n",
    "        end_time = time.time()\n",
    "        print(end_time - start_time)\n",
    "    return wrapper\n",
    "\n",
    "class Method(object):\n",
    "    \n",
    "    @decorator\n",
    "    def func(self):\n",
    "        time.sleep(0.8)\n",
    "        \n",
    "p1 = Method()\n",
    "p1.func() #函数调用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于类方法来说，都会有一个默认的参数self，它实际表示的是类的一个实例，所以在装饰器的内部函数wrapper也要传入一个参数me_instance就表示将类的实例p1传给wrapper，其他的用法都和函数装饰器相同。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. 类装饰器\n",
    "装饰器不仅可以是函数，还可以是类，相比函数装饰器，类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的\\__call\\__方法，当使用 @ 形式将装饰器附加到函数上时，就会调用此方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "decorator start\n",
      "func\n",
      "decorator end\n"
     ]
    }
   ],
   "source": [
    "class Decorator(object):\n",
    "    def __init__(self,f):\n",
    "        self.f = f\n",
    "    def __call__(self):  # __call__是一个特殊方法，它可将一个类实例变成一个可调用对象\n",
    "        print(\"decorator start\")\n",
    "        self.f()\n",
    "        print(\"decorator end\")\n",
    "        \n",
    "@Decorator\n",
    "def func():\n",
    "    print(\"func\")\n",
    "    \n",
    "func()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "要使用类装饰器必须实现类中的\\__call\\__()方法，就相当于将实例变成了一个方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. 内置装饰器\n",
    "python常用内置装饰器有属性(property)，类方法(classmethod)，静态方法(staticmethod)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.1 @property\n",
    "使调用类中的方法像引用类中的字段属性一样"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "通过实例引用属性\n",
      "Tom\n",
      "像引用属性一样调用@property修饰的方法\n",
      "hello Tom\n"
     ]
    }
   ],
   "source": [
    "class TestClass:\n",
    "    name = 'test'\n",
    "    def __init__(self,name):\n",
    "        self.name = name\n",
    "    \n",
    "    @property\n",
    "    def sayHello(self):\n",
    "        print(\"hello\",self.name)\n",
    "        \n",
    "cls = TestClass('Tom')\n",
    "print('通过实例引用属性')\n",
    "print(cls.name)\n",
    "print('像引用属性一样调用@property修饰的方法')\n",
    "cls.sayHello"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面再引用一个例子来介绍property的更常用的用法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5\n",
      "6\n",
      "30\n",
      "22\n"
     ]
    }
   ],
   "source": [
    "# 假设现在有个矩形，可以设置其宽、高。\n",
    "# 如果需要面积，我们可以使用类似于area()的方法来计算。\n",
    "# 如果需要周长，我们可以使用类似于perimeter()的方法来计算。\n",
    "# 对于Python这种动态语言来说，可以用perperty()加以包装：\n",
    "class Rectangle(object):\n",
    "    def __init__ (self,width,height):\n",
    "        self.__width = width\n",
    "        self.__height = height\n",
    "\n",
    "    def get_width(self):\n",
    "        return self.__width\n",
    "    def set_width(self, size):\n",
    "        self.__width= size\n",
    "    width=property(get_width,set_width)\n",
    "    \n",
    "    def get_height(self):\n",
    "        return self.__height\n",
    "    def set_height(self,size):\n",
    "        self.__height=size\n",
    "    height=property(get_height,set_height)\n",
    "\n",
    "    def area(self):\n",
    "        return self.width*self.height\n",
    "    area=property(area)\n",
    "    \n",
    "\n",
    "    def perimeter(self):\n",
    "        return (self.width+self.height)*2\n",
    "    perimeter=property(perimeter)\n",
    "# 这样，就使用了property()函数包装出了width、height、area、perimeter四个特性\n",
    "    \n",
    "rect=Rectangle(3,4)\n",
    "rect.width=5\n",
    "rect.height=6\n",
    "print(rect.width)\n",
    "print(rect.height)\n",
    "print(rect.area)\n",
    "print(rect.perimeter)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有了装饰器语法，以上代码可以简化为："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5\n",
      "6\n",
      "30\n",
      "22\n"
     ]
    }
   ],
   "source": [
    "class Rectangle(object):\n",
    "    def __init__ (self,width,height):\n",
    "        self.__width = width\n",
    "        self.__height = height\n",
    "\n",
    "    @property\n",
    "    def width(self):\n",
    "        return self.__width\n",
    "    \n",
    "    @width.setter\n",
    "    def width(self, size):\n",
    "        self.__width= size\n",
    "    \n",
    "    @property\n",
    "    def height(self):\n",
    "        return self.__height\n",
    "    \n",
    "    @height.setter\n",
    "    def height(self,size):\n",
    "        self.__height=size\n",
    "\n",
    "    @property\n",
    "    def area(self):\n",
    "        return self.width*self.height\n",
    "    \n",
    "    @property\n",
    "    def perimeter(self):\n",
    "        return (self.width+self.height)*2\n",
    "rect=Rectangle(3,4)\n",
    "rect.width=5\n",
    "rect.height=6\n",
    "print(rect.width)\n",
    "print(rect.height)\n",
    "print(rect.area)\n",
    "print(rect.perimeter)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.2 @staticmethod"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "将类中的方法修饰为静态方法，即类不需要创建实例的情况下，可以通过类名直接引用。到达将函数功能与实例解绑的效果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "通过实例引用方法\n",
      "5\n",
      "类名直接引用静态方法\n",
      "5\n"
     ]
    }
   ],
   "source": [
    "class TestClass:\n",
    "    name = 'test'\n",
    "    def __init__(self,name):\n",
    "        self.name = name\n",
    "    \n",
    "    @staticmethod\n",
    "    def fun(self,x,y):\n",
    "        return x+y\n",
    "    \n",
    "cls = TestClass('Tom')\n",
    "print('通过实例引用方法')\n",
    "print(cls.fun(None,2,3)) # 参数个数必须与定义中的个数保持一致，否则报错\n",
    "print('类名直接引用静态方法')\n",
    "print(TestClass.fun(None,2,3)) # 参数个数必须与定义中的个数保持一致，否则报错"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.3 @classmethod"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类方法的第一个参数是一个类，是将类本身作为操作的方法。类方法被哪个类调用，就传入哪个类作为第一个参数进行操作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "通过实例调用\n",
      "Normal car of BMW\n",
      "通过类名直接调用\n",
      "SUV car of Benz\n"
     ]
    }
   ],
   "source": [
    "class Car(object):\n",
    "    car = 'audi'\n",
    "    \n",
    "    @classmethod\n",
    "    def value(self,category): # 可定义多个参数，但第一个参数为类本身\n",
    "        print('%s car of %s'%(category,self.car))\n",
    "    \n",
    "class BMW(Car):\n",
    "    car = 'BMW'\n",
    "\n",
    "class Benz(Car):\n",
    "    car = 'Benz'\n",
    "    \n",
    "print('通过实例调用')\n",
    "baoma = BMW()\n",
    "baoma.value('Normal')\n",
    "\n",
    "print('通过类名直接调用')\n",
    "Benz.value('SUV')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. 装饰器链\n",
    "一个Python函数可以被多个装饰器修饰，若有多个装饰器时，它的执行顺序是从近到远依次执行。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<b><i>Hello<i><b>\n"
     ]
    }
   ],
   "source": [
    "def makebold(f):\n",
    "    return lambda: '<b>' + f() + '<b>'\n",
    "\n",
    "def makeitalic(f):\n",
    "    return lambda: '<i>' + f() + '<i>'\n",
    "\n",
    "@makebold\n",
    "@makeitalic\n",
    "def say():\n",
    "    return 'Hello'\n",
    "\n",
    "print(say())  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. 需要注意的地方"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 5.1 用functools.wraps装饰内层函数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用装饰器极大地复用了代码，但是他有一个缺点就是原函数的元信息不见了，比如函数的docstring、\\__name\\__、参数列表，如下面例子所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.logged.<locals>.with_logging>"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 装饰器\n",
    "def logged(func):\n",
    "    def with_logging(*args, **kwargs):\n",
    "        print(func.__name__)      # 输出 'with_logging'\n",
    "        print(func.__doc__)       # 输出 None\n",
    "        return func(*args, **kwargs)\n",
    "    return with_logging\n",
    "\n",
    "# 函数\n",
    "@logged\n",
    "def f(x):\n",
    "   \"\"\"does some math\"\"\"\n",
    "   return x + x * x\n",
    "\n",
    "logged(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "不难发现，函数 f 被with_logging取代了，当然它的docstring，\\__name\\__就是变成了with_logging函数的信息了。好在我们有functools.wraps，wraps本身也是一个装饰器，它能把原函数的元信息拷贝到装饰器里面的 func 函数中，这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.f>"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from functools import wraps\n",
    "def logged(func):\n",
    "    @wraps(func)\n",
    "    def with_logging(*args, **kwargs):\n",
    "        print(func.__name__)      # 输出 'f'\n",
    "        print(func.__doc__)       \n",
    "        return func(*args, **kwargs)\n",
    "    return with_logging\n",
    "\n",
    "@logged\n",
    "def f(x):\n",
    "   \"\"\"does some math\"\"\"\n",
    "   return x + x * x\n",
    "logged(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 5.2 修改外层变量时记得使用nonlocal"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "装饰器是对函数对象的一个高级应用。在编写装饰器的过程中，你会经常碰到内层函数需要修改外层函数变量的情况。就像下面这个装饰器一样："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "ename": "UnboundLocalError",
     "evalue": "local variable 'count' referenced before assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mUnboundLocalError\u001b[0m                         Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-29-480edbf23e93>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m     15\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mfoo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     16\u001b[0m     \u001b[1;32mpass\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 17\u001b[1;33m \u001b[0mfoo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32m<ipython-input-29-480edbf23e93>\u001b[0m in \u001b[0;36mdecorated\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m      7\u001b[0m     \u001b[1;32mdef\u001b[0m \u001b[0mdecorated\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      8\u001b[0m         \u001b[1;31m# 次数累加\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 9\u001b[1;33m         \u001b[0mcount\u001b[0m \u001b[1;33m+=\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     10\u001b[0m         \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34mf\"Count:{count}\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     11\u001b[0m         \u001b[1;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mUnboundLocalError\u001b[0m: local variable 'count' referenced before assignment"
     ]
    }
   ],
   "source": [
    "import functools\n",
    "\n",
    "def counter(func):\n",
    "    \"\"\"装饰器：记录并打印调用次数\"\"\"\n",
    "    count = 0\n",
    "    @functools.wraps(func)\n",
    "    def decorated(*args,**kwargs):\n",
    "        # 次数累加\n",
    "        count += 1\n",
    "        print(f\"Count:{count}\")\n",
    "        return func(*args,**kwargs)\n",
    "    return decorated\n",
    "\n",
    "@counter\n",
    "def foo():\n",
    "    pass\n",
    "foo()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了统计函数调用次数，我们需要在 decorated 函数内部修改外层函数定义的 count 变量的值。但是，上面这段代码是有问题的，在执行它时解释器会报错。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这个错误是由 counter 与 decorated 函数互相嵌套的作用域引起的。当解释器执行到 count+=1 时，并不知道 count 是一个在外层作用域定义的变量，它把 count 当做一个局部变量，并在当前作用域内查找。最终却没有找到有关 count 变量的任何定义，然后抛出错误。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了解决这个问题，我们需要通过 nonlocal 关键字告诉解释器：“count 变量并不属于当前的 local 作用域，去外面找找吧”，之前的错误就可以得到解决。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Count:1\n"
     ]
    }
   ],
   "source": [
    "import functools\n",
    "\n",
    "def counter(func):\n",
    "    \"\"\"装饰器：记录并打印调用次数\"\"\"\n",
    "    count = 0\n",
    "    @functools.wraps(func)\n",
    "    def decorated(*args,**kwargs):\n",
    "        # 次数累加\n",
    "        nonlocal count\n",
    "        count += 1\n",
    "        print(f\"Count:{count}\")\n",
    "        return func(*args,**kwargs)\n",
    "    return decorated\n",
    "\n",
    "@counter\n",
    "def foo():\n",
    "    pass\n",
    "foo()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
